To wrap up this chapter, let's examine the role of nullable data type using a final Console Application named NullableTypes. As you know, CLR data types have a fixed range and are represented as a type in the System namespace. For example, the System.Boolean data type can be assigned a value from the set {true, false}. Now, recall that all of the numerical data types (as well as the Boolean data type) are value types. Value types can never be assigned the value of null, as that is used to establish an empty object reference:
static void Main(string[] args) { // Compiler errors! // Value types cannot be set to null! bool myBool = null; int myInt = null; // OK! Strings are reference types. string myString = null; }
Since the release of .NET 2.0, it has been possible to create nullable data types. Simply put, a nullable type can represent all the values of its underlying type, plus the value null. Thus, if we declare a nullable bool, it could be assigned a value from the set {true, false, null}. This can be extremely helpful when working with relational databases, given that it is quite common to encounter undefined columns in database tables. Without the concept of a nullable data type, there is no convenient manner in C# to represent a numerical data point with no value.
To define a nullable variable type, the question mark symbol (?) is suffixed to the underlying data type. Do note that this syntax is only legal when applied to value types. If you attempt to create a nullable reference type (including strings), you are issued a compile-time error. Like a nonnullable variable, local nullable variables must be assigned an initial value before you can use them:
static void LocalNullableVariables() { // Define some local nullable types. int? nullableInt = 10; double? nullableDouble = 3.14; bool? nullableBool = null; char? nullableChar = 'a'; int?[] arrayOfNullableInts = new int?[10]; // Error! Strings are reference types! // string? s = "oops"; }
In C#, the ? suffix notation is a shorthand for creating an instance of the generic System. Nullable<T> structure type. It is important to understand that the System.Nullable<T> type provides a set of members that all nullable types can make use of.
For example, you are able to programmatically discover whether the nullable variable indeed has been assigned a null value using the HasValue property or the != operator. The assigned value of a nullable type may be obtained directly or via the Value property. In fact, given that the ? suffix is just a shorthand for using Nullable<T>, you could implement your LocalNullableVariables() method as follows:
static void LocalNullableVariablesUsingNullable() { // Define some local nullable types using Nullable<T>. Nullable<int> nullableInt = 10; Nullable<double> nullableDouble = 3.14; Nullable<bool> nullableBool = null; Nullable<char> nullableChar = 'a'; Nullable<int>[] arrayOfNullableInts = new int?[10]; }
As stated, nullable data types can be particularly useful when you are interacting with databases, given that columns in a data table may be intentionally empty (e.g., undefined). To illustrate, assume the following class, which simulates the process of accessing a database that has a table containing two columns that may be null. Note that the GetIntFromDatabase() method is not assigning a value to the nullable integer member variable, while GetBoolFromDatabase() is assigning a valid value to the bool? member:
class DatabaseReader { // Nullable data field. public int? numericValue = null; public bool? boolValue = true; // Note the nullable return type. public int? GetIntFromDatabase() { return numericValue; } // Note the nullable return type. public bool? GetBoolFromDatabase() { return boolValue; } }
Now, assume the following Main() method, which invokes each member of the DatabaseReader class, and discovers the assigned values using the HasValue and Value members as well as using the C# equality operator (not equal, to be exact):
static void Main(string[] args) { Console.WriteLine("***** Fun with Nullable Data *****\n"); DatabaseReader dr = new DatabaseReader(); // Get int from "database". int? i = dr.GetIntFromDatabase(); if (i.HasValue) Console.WriteLine("Value of 'i' is: {0}", i.Value); else Console.WriteLine("Value of 'i' is undefined."); // Get bool from "database". bool? b = dr.GetBoolFromDatabase(); if (b != null) Console.WriteLine("Value of 'b' is: {0}", b.Value); else Console.WriteLine("Value of 'b' is undefined."); Console.ReadLine(); }
The final aspect of nullable types to be aware of is that they can make use of the C# ?? operator. This operator allows you to assign a value to a nullable type if the retrieved value is in fact null. For this example, assume you wish to assign a local nullable integer to 100 if the value returned from GetIntFromDatabase() is null (of course, this method is programmed to always return null, but I am sure you get the general idea):
static void Main(string[] args) { Console.WriteLine("***** Fun with Nullable Data *****\n"); DatabaseReader dr = new DatabaseReader(); ... // If the value from GetIntFromDatabase() is null, // assign local variable to 100. int myData = dr.GetIntFromDatabase() ?? 100; Console.WriteLine("Value of myData: {0}", myData); Console.ReadLine(); }
The benefit of using the ?? operator is that it provides a more compact version of a traditional if/else condition. However, if you wish, you could have authored the following, functionally equivalent code to ensure that if a value comes back as null, it will indeed be set to the value 100:
// Long hand version of using ? : ?? syntax. int? moreData = dr.GetIntFromDatabase(); if (!moreData.HasValue) moreData = 100; Console.WriteLine("Value of moreData: {0}", moreData);